<?php

namespace maoxuner\LaravelJsonRpc\Server;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use maoxuner\LaravelJsonRpc\Entities\RequestObject;
use maoxuner\LaravelJsonRpc\Entities\ResponseObject;
use maoxuner\LaravelJsonRpc\Exceptions\Handler;
use maoxuner\LaravelJsonRpc\Server\Events\RequestHandled;
use maoxuner\LaravelJsonRpc\Server\Events\RequestReceived;
use maoxuner\LaravelJsonRpc\Server\Events\RpcHandled;
use maoxuner\LaravelJsonRpc\Server\Events\RpcReceived;
use Throwable;

/**
 * Transform laravel request to jsonrpc
 */
class Server
{
    public function __construct(
        protected Parser $parser,
        protected Router $router,
        protected Handler $handler
    ) {}

    /**
     * Handle single rpc request
     */
    public function handle(RequestObject $request): ResponseObject
    {
        RpcReceived::dispatch($request);

        try {
            // Call procedure
            $result = $this->router->dispatch($request->method, $request->params);

            // Build normal response
            $response = ResponseObject::build($result, $request->id);
        } catch (Throwable $throwable) {
            // Report throwable
            $this->handler->report($throwable);

            // Transform throwable
            $response = $this->handler->transform($throwable, $request->id);
        }

        RpcHandled::dispatch($request, $response);

        return $response;
    }

    /**
     * JsonRpc entrypoint
     */
    public function entrypoint(Request $request): JsonResponse|Response
    {
        RequestReceived::dispatch($request);

        try {
            $requestObject = $this->parser->extract($request);

            if ($requestObject instanceof Collection) {
                $responseObject = $requestObject->map([$this, 'handle'])
                    ->filter(fn ($res) => $this->isNotEmpty($res));
                $responseObject = $responseObject->isNotEmpty() ? $responseObject : null;
            } else {
                $responseObject = $this->handle($requestObject);
                $responseObject = $this->isNotEmpty($responseObject) ? $responseObject : null;
            }

            $response = $this->parser->compact($responseObject, $requestObject);
        } catch (Throwable $throwable) {
            // Report throwable
            $this->handler->report($throwable);

            // Transform throwable
            $response = $this->handler->render($request, $throwable);
        }

        RequestHandled::dispatch($request, $response);

        return $response;
    }

    protected function isEmpty(ResponseObject $responseObject): bool
    {
        return empty($responseObject->id) && empty($responseObject->error);
    }

    protected function isNotEmpty(ResponseObject $responseObject): bool
    {
        return ! $this->isEmpty($responseObject);
    }
}
